#include "dxgamesound.h"
#include "logger.h"

#define SAMPLES_PER_SEC 44100
#define BITS_PER_SAMPLE 16

using namespace std;

DXGameSound* DXGameSound::pInstance = 0;

DXGameSound::DXGameSound(DWORD samplesPerSec, WORD bitsPerSample)
{	
    pXAudio = NULL;
    pMasteringVoice = NULL;

    wfx.wFormatTag = WAVE_FORMAT_PCM;
    wfx.nChannels = 2;
    wfx.nSamplesPerSec = samplesPerSec; 
    wfx.wBitsPerSample = bitsPerSample;
    wfx.nBlockAlign = (wfx.nChannels * wfx.wBitsPerSample) / 8;
    wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
    wfx.cbSize = 0;

    //Init multithread distinct to execute COM object
    CoInitializeEx(NULL, COINIT_MULTITHREADED);
}

DXGameSound::~DXGameSound()
{	
    for (map<const char*, BYTE*>::iterator i = mSoundData.begin(); i != mSoundData.end(); ++i)
        if ((*i).second != NULL)
            delete[] (*i).second;
            
    for (map<const char*, IXAudio2SourceVoice*>::iterator i = mSourceVoice.begin(); i != mSourceVoice.end(); ++i)
        if ((*i).second != NULL)
        {	
            ((*i).second)->Stop(0, XAUDIO2_COMMIT_NOW);
            ((*i).second)->DestroyVoice();
        }
        
    if (pMasteringVoice != NULL)
        pMasteringVoice->DestroyVoice();
    if (pXAudio != NULL)
        pXAudio->Release();

    CoUninitialize();	//Release multithread 
}

DXGameSound* DXGameSound::GetInstance()
{
    if (pInstance == 0)
    {
        pInstance = new DXGameSound(SAMPLES_PER_SEC, BITS_PER_SAMPLE);
    }
    return pInstance;
}

void DXGameSound::Destroy()
{
    if (pInstance != 0)
    {
        delete pInstance;
    }
    return;
}

bool DXGameSound::Init()
{	
    UINT flag = 0;
    #ifdef _DEBUG
        flag |= XAUDIO2_DEBUG_ENGINE;
    #endif
    if FAILED(XAudio2Create(&pXAudio, flag, XAUDIO2_DEFAULT_PROCESSOR))
    {	
        (Logger::GetLogger("DXGameSound")).Error("Can't init XAudio2!");
        return false;
    }
    if FAILED(pXAudio->CreateMasteringVoice(&pMasteringVoice,
                                            XAUDIO2_DEFAULT_CHANNELS,
                                            XAUDIO2_DEFAULT_SAMPLERATE,
                                            0,
                                            0,
                                            NULL))
    {	
        (Logger::GetLogger("DXGameSound")).Error("Can't init mastering voice!");
        return false;
    }
    return true;
}

bool DXGameSound::LoadBackgroundSound(const char* nameSound, const char* aliasSound)
{	
    if (mSourceVoice.find(aliasSound) != mSourceVoice.end())
        UnLoadSound(aliasSound);
    else
    {	
        mSourceVoice[aliasSound] = NULL;
        mSoundData[aliasSound] = NULL;
    }

    //Define size of file sound
    ifstream fileSound;
    fileSound.open(nameSound, ios::in | ios::binary);
    if (fileSound.fail())
    {	
        (Logger::GetLogger("DXGameSound")).Error(string("Can't open file sound : ") + nameSound);
        return false;
    }
    fileSound.seekg(0, ios::end);	//Set pointer file to end of file
    unsigned int fileLength = (unsigned int)fileSound.tellg();	//Get pointer file
    fileSound.seekg(0, ios::beg);	//Set pointer file to begin of file

    mSoundData[aliasSound] = new BYTE [fileLength];
    if (mSoundData[aliasSound] == NULL)
    {	
        (Logger::GetLogger("DXGameSound")).Error(string("Can't locate memory for data of file sound : ") + nameSound);
        return false;
    }
    fileSound.read((char*)mSoundData[aliasSound], fileLength);
    fileSound.close();

    //Infomation audio buffer
    XAUDIO2_BUFFER audioBuffer;									
    audioBuffer.Flags = XAUDIO2_END_OF_STREAM;
    audioBuffer.AudioBytes = fileLength;
    audioBuffer.pAudioData = mSoundData[aliasSound];
    audioBuffer.PlayBegin = 0;
    audioBuffer.PlayLength = 0;
    audioBuffer.LoopBegin = 0;
    audioBuffer.LoopLength = 0;
    audioBuffer.LoopCount = XAUDIO2_LOOP_INFINITE;	//Loop forever for background sound
    audioBuffer.pContext = NULL;

    mAudioBuffer[aliasSound] = audioBuffer;

    if FAILED(pXAudio->CreateSourceVoice(&mSourceVoice[aliasSound],
                                        &wfx,
                                        0,
                                        XAUDIO2_DEFAULT_FREQ_RATIO,
                                        NULL,
                                        NULL,
                                        NULL))
    {	
        (Logger::GetLogger("DXGameSound")).Error("Can't create source voice!");
        return false;
    }
    return true;
}

bool DXGameSound::LoadSound(const char* nameSound, const char* aliasSound)
{	
    if (mSourceVoice.find(aliasSound) != mSourceVoice.end())
        UnLoadSound(aliasSound);
    else
    {	
        mSourceVoice[aliasSound] = NULL;
        mSoundData[aliasSound] = NULL;
    }

    //Define size of file sound
    ifstream fileSound;
    fileSound.open(nameSound, ios::in | ios::binary);
    if (fileSound.fail())
    {	
        (Logger::GetLogger("DXGameSound")).Error(string("Can't open file sound : ") + nameSound);
        return false;
    }
    fileSound.seekg(0, ios::end);	//Set pointer file to end of file
    unsigned int fileLength = (unsigned int)fileSound.tellg();	//Get pointer file
    fileSound.seekg(0, ios::beg);	//Set pointer file to begin of file

    mSoundData[aliasSound] = new BYTE [fileLength];
    if (mSoundData[aliasSound] == NULL)
    {	
        (Logger::GetLogger("DXGameSound")).Error(string("Can't locate memory for data of file sound : ") + nameSound);
        return false;
    }
    fileSound.read((char*)mSoundData[aliasSound], fileLength);
    fileSound.close();

    //Infomation audio buffer
    XAUDIO2_BUFFER audioBuffer;									
    audioBuffer.Flags = XAUDIO2_END_OF_STREAM;
    audioBuffer.AudioBytes = fileLength;
    audioBuffer.pAudioData = mSoundData[aliasSound];
    audioBuffer.PlayBegin = 0;
    audioBuffer.PlayLength = 0;
    audioBuffer.LoopBegin = 0;
    audioBuffer.LoopLength = 0;
    audioBuffer.LoopCount = 0;
    audioBuffer.pContext = NULL;

    mAudioBuffer[aliasSound] = audioBuffer;

    if FAILED(pXAudio->CreateSourceVoice(&mSourceVoice[aliasSound],
                                        &wfx,
                                        0,
                                        XAUDIO2_DEFAULT_FREQ_RATIO,
                                        NULL,
                                        NULL,
                                        NULL))
    {	
        (Logger::GetLogger("DXGameSound")).Error("Can't create source voice!");
        return false;
    }
    return true;
}

void DXGameSound::UnLoadSound(const char* aliasSound)
{	
    map<const char*, IXAudio2SourceVoice*>::iterator i = mSourceVoice.find(aliasSound);
    if ((i != mSourceVoice.end()) && ((*i).second != NULL))
    {	
        if ((*i).second != NULL)
        {	
            ((*i).second)->Stop(0, XAUDIO2_COMMIT_NOW);
            ((*i).second)->DestroyVoice();
            mSourceVoice.erase(i);
        }
        delete[] mSoundData[aliasSound];
        mSoundData.erase(aliasSound);
    }
    return;
}

void DXGameSound::Play(const char* aliasSound)
{	
    map<const char*, IXAudio2SourceVoice*>::iterator i = mSourceVoice.find(aliasSound);
    if ((i != mSourceVoice.end()) && ((*i).second != NULL))
    {	
        Stop(aliasSound);
        if FAILED(((*i).second)->SubmitSourceBuffer(&mAudioBuffer[aliasSound], NULL))
        {	
            (Logger::GetLogger("DXGameSound")).Error(string("Can't submit buffer of alias sound : ") + aliasSound);
            return;
        }
        ((*i).second)->Start(0, XAUDIO2_COMMIT_NOW);
    }
    return;
}

void DXGameSound::Stop(const char* aliasSound)
{	
    map<const char*, IXAudio2SourceVoice*>::iterator i = mSourceVoice.find(aliasSound);
    if ((i != mSourceVoice.end()) && ((*i).second != NULL))
    {	
        ((*i).second)->Stop(0, XAUDIO2_COMMIT_NOW);
        ((*i).second)->FlushSourceBuffers();
    }
    return;
}

void DXGameSound::Pause(const char* aliasSound)
{	
    map<const char*, IXAudio2SourceVoice*>::iterator i = mSourceVoice.find(aliasSound);
    if ((i != mSourceVoice.end()) && ((*i).second != NULL))
        ((*i).second)->Stop(0, XAUDIO2_COMMIT_NOW);
    return;
}

void DXGameSound::Resume(const char* aliasSound)
{	
    map<const char*, IXAudio2SourceVoice*>::iterator i = mSourceVoice.find(aliasSound);
    if ((i != mSourceVoice.end()) && ((*i).second != NULL))
    {	
        ((*i).second)->Stop(0, XAUDIO2_COMMIT_NOW);
        ((*i).second)->Start(0, XAUDIO2_COMMIT_NOW);
    }
    return;
}
